'''
进程的概念

可执行的代码就叫程序。
正在运行着的代码+需要的一些资源就是进程。

例子：QQ没打开的时候是程序，打开了之后是进程。

fork

fork()调用一次，返回两次。从返回处开始，父子进程的代码就是一样的了。
意思大概是这样的：
'''


# import os,time
#
# ret = os.fork()
# if ret == 0:
#     print('我是子进程')
# else:
#     time.sleep(1)
#     print('我是父进程')


"""
从ret = os.fork() 这句往下，父进程和子进程都会往下执行：
if ret == 0:
    print('我是子进程')
else:
    print('我是父进程')
"""

'''fork()将0返回给子进程，将子进程的ID返回给父进程。
获取自己进程的ID用os.getpid(),获取父进程的ID用os.getppid()。
用fork()的话，父进程不会等待子进程结束。（也就是说，主进程可以在子进程结束之前结束）

进程的执行过程：
操作系统在面对多进程的时候，会根据自身的调度算法来进行执行。所以你不知道每个进程执行了多少代码后就被CPU给踢出来了。这都是操作系统自己决定的，程序员管不了。

进程的资源是独立的，所以程序中的全局变量在多进程中是不共享的。
什么意思呢？
'''

# import os
# import time
# nums = 1
# ret = os.fork()
# if not ret:
#         print('子进程')
#         # time.sleep(2)
#         nums += 1
#         print('子进程的nums：%s'%nums)
# else:
#         time.sleep(1)
#         print('父进程')
#
# print(nums)


'''
我们在子进程中对nums进行了修改，并让主线程睡了1秒来保证先执行子进程，但是我们可以看到，子进程修改了nums的值对主进程中的nums的值并没有任何影响。
如果想共享咋办？？？要进程间通信技术。
'''
'''
multiprocessing-Process

很遗憾，fork()在windows上不能用。所以我们用fork写不出跨平台的程序。
但是我们的python如此强大，它肯定能写出来跨平台的多进程程序。
用multiprocessing就可以解决这个问题。
注意，在windows平台上用Process写多进程时，需要在main函数中执行(Linux中就无所谓了)：
'''
# import os
# from multiprocessing import Process
#
# def pr():
#     print('aaa')
# if __name__ == '__main__':
#     p1 = Process(target=pr)
#     p1.start()
#     p2 = Process(target=pr)
#     p2.start()

"""
不这么写会报错。
用Process写的多进程程序，主进程会等待子进程结束后再结束（注意，是结束，而不是执行哦）。意思就是，如果启动子进程是主线程的最后一条语句的话，我们不用care这个问题，因为主进程没有语句了，只需要等待子进程结束然后自己也结束即可。
如果开启子进程后主进程还想执行点东西，是可以的。因为主进程和子进程一起抢占CPU，主进程并不会等子进程结束之后再执行自己的代码。
如果我们现在有一个需求：主进程需要等待子进程执行完毕后再执行一些语句，那怎么办呢？ 用join()。 join()起的作用是阻塞主进程。
"""
# import os
# from multiprocessing import Process
# import time
# def pr(i):
#     print('aaa%d'%i)
#     time.sleep(3)
# if __name__ == '__main__':
#     p1 = Process(target=pr,args=(1,))
#     p1.start()
#     p1.join(1) # 也可以给join设置一个timeout
#     print('hhh')

"""
join()解除阻塞状态有两个可选条件：
1、timeout超时
2、子进程运行结束
比如上面这样一个程序，子进程执行1秒后，主进程就可以输出hhh了。

terminate() # 直接杀死子进程。
示例：
"""
# import os
# from multiprocessing import Process
# import time
# def pr(i): # 子进程需要执行的代码
#     print('aaa%d'%i)
#     time.sleep(3)
#     print('time out')
# if __name__ == '__main__':
#     p1 = Process(target=pr,args=(1,))
#     p1.start()
#     p1.join(1) # 也可以给join设置一个timeout
#     print('hhh')
#     p1.terminate() # 直接强制杀掉p1这个进程

"""
multiprocessing-继承Process类

也可以通过继承Process类来创建进程：
只需要写一个继承自Process的类，然后重写类的run()方法即可。
"""

# import os
# from multiprocessing import Process
# import time
#
# class Demo(Process): # 继承自Process
#     def run(self):
#         print('wocao')
#
# if __name__ == '__main__':
#     d = Demo()
#     d.start() # 开启进程

"""
进程池Pool

池的作用一般都是缓冲。（蓄水池、备胎池2333333333）
"""

# from multiprocessing import Pool
# import time
# def r(i):
#     print('hhh%d'%i)
#     time.sleep(0.5)
#
# if __name__ == '__main__':
#     po = Pool(3)  # 定义了一个最大进程数为3的进程池
#     for i in range(10):
#         po.apply_async(r,(i,)) # r 为要调用的目标，后面可用一个元组给出目标的参数
#         # 循环10次的目的是把任务加入到进程池中（for是一下子就执行完了，跟进程池没关系）
#         # 我们会发现输出是3个3个往外蹦，因为进程池有3个进程
#     po.close() # 关闭进程池 关闭后不再接收新的请求
#     po.join()

'''
注意，用进程池写的程序，主线程不会等待子进程结束后再结束。而且主进程结束之后子进程也会跟着结束，并不像fork那样，主进程结束之后子进程还能做一些事情。在Pool中，主进程死了，子进程就没了。所以如果要想让主进程等待子进程，需要加上join()。
而且，在po.apply_async(r,(i,))时，进程池中的子进程就开始执行了，并不是非要等任务全部添加完成再执行。
代码：
'''
# from multiprocessing import Pool
# import time
# def r(i):
#     print('hhh%d'%i)
#     time.sleep(0.5)
#
# if __name__ == '__main__':
#     po = Pool(3)  # 定义了一个最大进程数为3的进程池
#     for i in range(100000):
#         po.apply_async(r,(i,)) # r 为要调用的目标，后面可用一个元组给出目标的参数
#         print(1)
#         # 我们会发现输出是3个3个往外蹦，因为进程池有3个进程
#     print('wocao')
#     po.close() # 关闭进程池 关闭后不再接收新的请求
#
#     po.join()

"""
往进程池中添加的任务的数量是不限制的。
进程池就相当于开了个公司，然后招了3个小弟给你干活。来了活就交给小弟干，别管多少活都要交给小弟干。要合理的设置小弟数量（因为切换时耗费大量资源）。
po.apply_async(r,(i,))这种方式是非阻塞方式。
接下来我们看看阻塞apply的怎么写：

"""
# from multiprocessing import Pool
# import time
# def r(i):
#     print('hhh%d'%i)
#     time.sleep(0.5)
#
# if __name__ == '__main__':
#     po = Pool(3)
#     for i in range(100000):
#         po.apply(r,(i,))
#     print('wocao')
#     po.close()
#
#     po.join()

"""
其实就是把apply_async改成了apply。
apply会一个一个的往外蹦，并不会3个3个的往外蹦。
"""


"""
进程间通信Queue

我们创造出来进程，就是想让很多进程协作来完成复杂的任务，所以说进程间必须要可以通信。那怎么实现进程间通信呢？用队列Queue。
既然我们是用Queue来实现进程间通信的，那么我们这就来先好好学一下Queue。
我们先来了解一下Queue的两个方法：get和put：

"""
# from multiprocessing import Queue

# q = Queue(3)# 创建一个最大容量是3的队列
# # q = Queue() # 创建一个无限容量的队列
# # q.put(1)
# # q.put(1)
# # q.put(1)
# # q.put(2) # 这句会阻塞。 因为队列满了，放不进去。
#
# q.get() # 这句会阻塞。因为队列空了，取不出来。

"""
然后再来看一下返回队列实际长度的qsize方法：
"""
# from multiprocessing import Queue
#
# q = Queue(3) # 创建一个最大容量是3的队列
# q.put(2)
# print(q.qsize()) # 返回队列的实际长度

'''
然后再来看一下返回队列是空还是满的方法empty 和 full：
'''
# from multiprocessing import Queue
#
# q = Queue(3)
# q.put(2)
# print(q.empty()) # 队列为空则返回True
# print(q.full()) # 队列满了返回False

"""
所以为了避免程序处于阻塞状态，我们在使用get和put方法之前需要判空和判满.
当然，我们也可以配合另外两个方法put_nowait() 和 get_nowait()，用异常处理来解决这个问题：
"""
# q.put_nowait(obj)
# q.get_nowait()

# 这样的话异常时不会阻塞，只会抛出异常

'''
不能有两个进程同时get，这样会造成时序错误。
来看一个实例：
'''
# from multiprocessing import Queue,Process
# import os,time,random
#
#
# def write(q):
#     for value in ['A','B','C']:
#         print('put %s to queue' % value)
#         q.put(value)
#         time.sleep(random.random())
#
# def read(q):
#     while True:
#         if not q.empty():
#             value = q.get()
#             print('get  %s from queue' % value)
#             time.sleep(random.random())
#         else:
#             break
#
# if __name__ == '__main__':
#
#     q = Queue()
#     pw = Process(target=write,args=(q,))
#     pr = Process(target=read, args=(q,))
#
#     pw.start()
#     pw.join()
#     pr.start()
#     pr.join()

'''
刚刚我们说的都是Process创造的多线程程序怎么用Queue，那么进程池Pool弄出来的多进程该怎么用Queue呢？
应该用即可：
'''
# from multiprocessing import Manager,Pool
# q = Manager().Queue()








